#ifndef Scenic_Renderer_Graphics_h
#define Scenic_Renderer_Graphics_h

#ifdef DIRECTX
#include <windows.h>
#include <d3dx9.h>
#endif
#ifdef OPT_OPENGL
#include <GL/gl.h>
#ifdef OPT_X11
#include <X11/X.h>
#include <GL/glx.h>
#endif
#include "GLExtensions.h"
#endif

#include "DynArray.h"
#include "Resource.h"

namespace Scenic
{
namespace Renderer
{
struct RGBAFloat;

enum Format
{
	FMT_A8R8G8B8 = 0,
	FMT_R8G8B8 = 1,
	FMT_L8 = 2,
	FMT_A8 = 3,
	FMT_AL8 = 4,
	FMT_COUNT = 5
};

#ifdef DIRECTX
enum SamplerStateType
{
	SAMPLER_ADDRESSU = 1,
	SAMPLER_ADDRESSV = 2,
	SAMPLER_MAG_FILTER = 5,
	SAMPLER_MIN_FILTER = 6,
	SAMPLER_MIP_FILTER = 7
};

enum Filter 
{
	FILTER_NONE = 0,
	FILTER_POINT = 1,
	FILTER_LINEAR = 2
};

enum Address 
{
	ADDRESS_WRAP = 1,
	ADDRESS_CLAMP = 3
};

enum BlendFactor
{
	BLEND_ZERO = D3DBLEND_ZERO,
	BLEND_ONE = D3DBLEND_ONE,
	BLEND_SRC_ALPHA = D3DBLEND_SRCALPHA,
	BLEND_ONE_MINUS_SRC_ALPHA = D3DBLEND_INVSRCALPHA
};

enum BlendOp
{
	BLENDOP_ADD = 1,
	BLENDOP_SUBTRACT = 2,
	BLENDOP_REVSUBTRACT = 3
};

enum State
{
	STATE_BLENDENABLE = D3DRS_ALPHABLENDENABLE,
	STATE_BLENDOP = D3DRS_BLENDOP,
	STATE_BLENDOPALPHA = D3DRS_BLENDOPALPHA
};

enum Boolean
{
	BOOLEAN_FALSE = FALSE,
	BOOLEAN_TRUE = TRUE
};

enum Usage
{
	USAGE_RENDER_TARGET = 1
};
#endif
#ifdef OPT_OPENGL
enum SamplerStateType
{
	SAMPLER_ADDRESSU = GL_TEXTURE_WRAP_S,
	SAMPLER_ADDRESSV = GL_TEXTURE_WRAP_T,
	SAMPLER_MAG_FILTER = GL_TEXTURE_MAG_FILTER,
	SAMPLER_MIN_FILTER = GL_TEXTURE_MIN_FILTER,
	SAMPLER_MIP_FILTER = -1
};

enum Filter 
{
	FILTER_NONE = 0,
	FILTER_POINT = GL_NEAREST,
	FILTER_LINEAR = GL_LINEAR
};

enum Address 
{
	ADDRESS_WRAP = GL_REPEAT,
	ADDRESS_CLAMP = GL_CLAMP_TO_EDGE
};

enum BlendFactor
{
	BLEND_ZERO = GL_ZERO,
	BLEND_ONE = GL_ONE,
	BLEND_SRC_ALPHA = GL_SRC_ALPHA,
	BLEND_ONE_MINUS_SRC_ALPHA = GL_ONE_MINUS_SRC_ALPHA
};

enum BlendOp
{
	BLENDOP_ADD = GL_FUNC_ADD,
	BLENDOP_SUBTRACT = GL_FUNC_SUBTRACT,
	BLENDOP_REVSUBTRACT = GL_FUNC_REVERSE_SUBTRACT
};

enum State
{
	STATE_BLENDENABLE = GL_BLEND,
	STATE_BLENDOP = -1,
	STATE_BLENDOPALPHA = -2
};

enum Boolean
{
	BOOLEAN_FALSE = GL_FALSE,
	BOOLEAN_TRUE = GL_TRUE
};

enum Usage
{
	USAGE_RENDER_TARGET = 1
};

#endif

class Surface;
class Texture;

struct Capabilities
{
	bool nonPow2Conditional;
	bool textureFormats[FMT_COUNT];
	bool autogenFormats[FMT_COUNT];
	int maxTextureWidth;
	int maxTextureHeight;
#ifdef DIRECTX
	D3DCAPS9 d3dcaps;
#endif
};

class Device : public Resource
{
	Capabilities caps;
#ifdef DIRECTX
	IDirect3DDevice9 *device;
	IDirect3DSwapChain9 *swapChain;
	bool implicitSwapChain;
	HWND hwnd;
#endif
#ifdef OPT_OPENGL
	int textureModulation[2];
	int textureTarget[2];
	int textureSamplerStates[2][4];
#ifdef OPT_X11
	Display *display;
	Surface *drawSurface;
	Surface *windowSurface;
#endif
#endif
public:
#ifdef DIRECTX
	Device(HWND hwnd);

	IDirect3DDevice9 *getDevice() { return device; }
	void init();
#endif
#ifdef OPT_OPENGL
#ifdef OPT_X11
	Device(Window window);
#endif
#endif

	~Device();

	void reset();
	void initializeViewport(int width, int height);
	bool beginScene();
	void endScene();
	void present();
	void clear(const RGBAFloat &color);
	void setRenderTarget(Surface *surface);
	void setRenderTarget(Texture *texture);
	Surface *getRenderTarget();
	void setTexture(int stage, Texture *texture);
	void setSamplerState(int stage, SamplerStateType type, int value);
	void setSamplerStates(int stage, Filter filter, Address address);
	void setTextureStageModulation(int stage, int modulation);
	void setState(State state, int value);
	void setBlendFunc(BlendFactor srcBlend, BlendFactor dstBlend);
	static Capabilities &getCaps();

	static int getVisualId(int screen);
	static int getFormatSize(Format format);
#ifdef OPT_OPENGL
	void makeCurrent();
	int getTotalModulation();
#endif

private:
	void checkCaps();
	void initState();
#ifdef OPT_OPENGL
#ifdef OPT_X11
	static Device *getShareDevice();
#endif
#endif

	friend class Surface;
	friend class Texture;
};

struct SurfaceInfo
{
	int width;
	int height;
};

class Surface
{
	Device *device;
	int refCount;
	int format;
	int surfaceFormat;
	Texture *texture;
	int level;
#ifdef DIRECTX
	IDirect3DSurface9 *surface;

	Surface(Texture *texture, IDirect3DSurface9 *surface);
#endif
#ifdef OPT_OPENGL
#ifdef OPT_X11
	bool isWindow;
	GLXPbuffer pbuffer;
	Drawable drawable;
	GLXContext context;
	XVisualInfo *visinfo;
	
	Surface(Device *device, Window window);
	Surface(Texture *texture, int level);
	
	void createPBuffer();
	void beginRender();
	void endRender();
#endif
#endif
public:
#ifdef DIRECTX
#endif
	~Surface();

	void release();
	void addRef();
	SurfaceInfo getInfo();

	friend class Device;
	friend class Texture;
};

class CriticalSection
{
#ifdef DIRECTX
	CRITICAL_SECTION criticalSection;
#endif
public:
	CriticalSection();
	~CriticalSection();

	void enter();
	void leave();
};

class Synchronized
{
	CriticalSection *criticalSection;
public:
	Synchronized(CriticalSection &criticalSection) 
	{ 
		this->criticalSection = &criticalSection;
		this->criticalSection->enter(); 
	}
	~Synchronized()
	{
		criticalSection->leave();
	}
};

class Texture : public Resource
{
	int refCount;
	Format format;
	Format textureFormat;
	char *localData;
	int width;
	int height;
	int usage;
	int levels;
	CriticalSection criticalSection;
	
#ifdef DIRECTX
	static DynArray<Texture*> textures;
	int id;
	IDirect3DTexture9 *texture;
	bool autogenMipmaps;

	static void freeAll();
	static void loadAll();

	void free();
	void load();
	void createTexture();
	void writeTexture(int level, int x, int y, int width, int height, void *data, int pitch);
	void generateMipmaps();
#endif
#ifdef OPT_OPENGL
	GLenum target;
	GLuint texture;
	GLuint framebuffer;
	
	void load();
	void unload();
	void createTexture();
	void writeTexture(int level, int x, int y, int width, int height, void *data, int pitch);
#endif
public:
	Texture(int width, int height, Format format, int usage, int levels = 1);
	~Texture();

	void release(bool isAsync = false);
	void addRef();

	void write(int level, int x, int y, int width, int height, void *data, int pitch);

	Surface *getSurface(int level);

	friend class Device;
	friend class Surface;
};

}
}

#endif
